import * as vscode from 'vscode';
import * as path from 'path';
import { disposeAll, getNonce } from './util';

/**
 * Define the document (the data model) used for BLP files.
 */
export class BLPDocument implements vscode.CustomDocument {

	private isDisposed = false;
	private disposables: vscode.Disposable[] = [];

	public constructor(public uri: vscode.Uri) { }

	private readonly _onDidDispose = this._register(new vscode.EventEmitter<void>());
	/**
	 * Fired when the document is disposed of.
	 */
	public readonly onDidDispose = this._onDidDispose.event;

	private readonly _onDidChangeDocument = this._register(new vscode.EventEmitter<void>());
	/**
	 * Fired to notify webviews that the document has changed.
	 */
	public readonly onDidChangeContent = this._onDidChangeDocument.event;

	/**
	 * Called by VS Code when there are no more references to the document.
	 * 
	 * This happens when all editors for it have been closed.
	 */
	dispose(): void {
		this._onDidDispose.fire();
		if (this.isDisposed) {
			return;
		}
		this.isDisposed = true;
		disposeAll(this.disposables);
	}

	private _register<T extends vscode.Disposable>(value: T): T {
		if (this.isDisposed) {
			value.dispose();
		} else {
			this.disposables.push(value);
		}
		return value;
	}
}

/**
 * Provider for Blp.
 */
export class BLPEditorProvider implements vscode.CustomReadonlyEditorProvider<BLPDocument> {

	public static register(context: vscode.ExtensionContext): vscode.Disposable {
		const provider = new BLPEditorProvider(context);
		const providerRegistration = vscode.window.registerCustomEditorProvider(BLPEditorProvider.viewType, provider);
		return providerRegistration;
	}

	private static readonly viewType = 'wc3.blp';


	constructor(
		private readonly _context: vscode.ExtensionContext
	) { }

	openCustomDocument(
		uri: vscode.Uri,
		openContext: { backupId?: string },
		_token: vscode.CancellationToken
	): BLPDocument {
		const document = new BLPDocument(uri);
		const listeners: vscode.Disposable[] = [];

		document.onDidDispose(() => disposeAll(listeners));

		return document;
	}

	resolveCustomEditor(
		document: BLPDocument,
		webviewPanel: vscode.WebviewPanel,
		_token: vscode.CancellationToken
	): void {

		// Setup initial content for the webview
		webviewPanel.webview.options = {
			enableScripts: true,
		};
		webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview, document);

		// Wait for the webview to be properly ready before we init
		webviewPanel.webview.onDidReceiveMessage(e => {
			if (e.type === 'ready') {
				this.postMessage(webviewPanel, 'init', {});
			}
		});
	}


	private getMediaPath(scheme: string, mediaFile: string): vscode.Uri {
		return vscode.Uri.file(path.join(this._context.extensionPath, 'media', mediaFile))
			.with({ scheme: scheme });
	}

	private getSettings(uri: vscode.Uri): string {
		const initialData = {
			fileToLoad: uri.toString(),
		}
		return `<meta id="vscode-blpviewer-data" data-settings="${JSON.stringify(initialData).replace(/"/g, '&quot;')}">`
	}

	private getScripts(scheme: string, nonce: string): string {
		const scripts = [
			this.getMediaPath(scheme, 'war3-model.browser.js'),
			this.getMediaPath(scheme, 'blp/blp.js'),
		];
		return scripts
			.map(source => `<script nonce="${nonce}" src="${source}"></script>`)
			.join('\n');
	}

	/**
	 * Get the static html used for the editor webviews.
	 */
	private getHtmlForWebview(webview: vscode.Webview, document: BLPDocument): string {
		const fileToLoad = document.uri.scheme === "file" ?
			webview.asWebviewUri(vscode.Uri.file(document.uri.fsPath)) :
			document.uri;

		const styleUri = webview.asWebviewUri(vscode.Uri.file(
			path.join(this._context.extensionPath, 'media', 'blp/blp.css')
		));

		const mediaUri = webview.asWebviewUri(vscode.Uri.file(
			path.join(this._context.extensionPath, 'media')
		));

		// Use a nonce to whitelist which scripts can be run
		const nonce = getNonce();

		return /* html */`
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="UTF-8">

				<!--
				Use a content security policy to only allow loading images from https or from our extension directory,
				and only allow scripts that have a specific nonce.
				-->

				<meta http-equiv="Content-Security-Policy" content="default-src ${webview.cspSource} 'self' 'unsafe-eval' blob: data:; img-src ${webview.cspSource} 'self' 'unsafe-eval' blob: data:; style-src ${webview.cspSource} 'unsafe-inline'; script-src ${webview.cspSource} 'self' 'unsafe-eval' blob: data:;">

				<meta name="viewport" content="width=device-width, initial-scale=1.0">
				<link href="${styleUri}" rel="stylesheet" />
				<base href="${mediaUri}/">
				${this.getSettings(fileToLoad)}
				<title>MDX预览</title>
				
			</head>
			<body>
				<div class="container" dropzone="copy">
					<div class="preview"></div>
				</div>
			</body>
				${this.getScripts('vscode-resource', nonce)}
			</html>`;
	}


	private readonly _callbacks = new Map<number, (response: any) => void>();

	private postMessage(panel: vscode.WebviewPanel, type: string, body: any): void {
		panel.webview.postMessage({ type, body });
	}

	private onMessage(document: BLPDocument, message: any) {
		switch (message.type) {
			case 'response':
				const callback = this._callbacks.get(message.requestId);
				callback?.(message.body);
				return;
		}
	}

}